package redisv9ex

import (
	"context"
	"dgo/goutils/utils"
	"fmt"
	"gitee.com/ymofen/gobase"
	"github.com/redis/go-redis/v9"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type RedisOperator struct {
	lk           sync.RWMutex
	busyflag     int32
	clt          redis.Cmdable
	redisOpt     *redis.Options
	PoolSize     int
	LastErr      string
	hincrbyIfSha string

	setIfSha  string
	hsetIfSha string
}

func NewRedisOperator(clt redis.Cmdable) *RedisOperator {
	rval := &RedisOperator{clt: clt}
	return rval
}

func (this *RedisOperator) Close() error {
	this.clt = nil
	return nil
}

func (this *RedisOperator) withDeadlineCtx() context.Context {
	ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*1))
	return ctx
}

func (this *RedisOperator) Ping() error {
	_, err := this.clt.Ping(context.Background()).Result()
	return err
}

func (this *RedisOperator) SetKeyValue(key string, val string, exp time.Duration) error {
	_, err := this.clt.Set(this.withDeadlineCtx(), key, val, exp).Result()
	if err != nil {
		this.LastErr = fmt.Sprintf("[%s]Set fail:%s", utils.NowString(), err.Error())
	}
	return err
}

func (this *RedisOperator) Del(key ...string) error {
	_, err := this.clt.Del(this.withDeadlineCtx(), key...).Result()
	if err != nil {
		this.LastErr = fmt.Sprintf("[%s]Del fail:%s", utils.NowString(), err.Error())
	}
	return err
}

func (this *RedisOperator) GetKeys(pattern string) []string {
	rval, err := this.clt.Keys(this.withDeadlineCtx(), pattern).Result()
	if err != nil {
		if err == redis.Nil { // redis中没有值
			return rval
		}
		this.LastErr = fmt.Sprintf("[%s]Get fail:%s", utils.NowString(), err.Error())
		return nil
	}
	return rval
}

func (this *RedisOperator) StringByName(key string, failresult string) string {
	rval, err := this.clt.Get(this.withDeadlineCtx(), key).Result()
	if err != nil {
		if err == redis.Nil {
			return rval
		}
		this.LastErr = fmt.Sprintf("[%s]Get fail:%s", utils.NowString(), err.Error())
		return failresult
	}
	return rval
}

func (this *RedisOperator) HGetFields(key string, fields ...string) ([]interface{}, error) {
	rval, err := this.clt.HMGet(this.withDeadlineCtx(), key, fields...).Result()
	if err == redis.Nil {
		return nil, nil
	}
	if err != nil {
		return rval, err
	}
	return rval, nil
}

func (this *RedisOperator) HStringByName(key, field string) (string, error) {
	rval, err := this.clt.HGet(this.withDeadlineCtx(), key, field).Result()
	if err == redis.Nil {
		return rval, nil
	}
	if err != nil {
		return "", err
	}
	return rval, nil
}

func (this *RedisOperator) HIntByName(key, field string) (int, error) {
	rval, err := this.clt.HGet(this.withDeadlineCtx(), key, field).Result()
	if err == redis.Nil {
		return 0, nil
	}
	if err != nil {
		return 0, err
	}
	return utils.StrToIntDef(rval, 0), nil
}

func (this *RedisOperator) SAdd(key string, members ...interface{}) (int64, error) {
	return this.clt.SAdd(this.withDeadlineCtx(), key, members...).Result()
}

// 删除成功：1, 元素不存在返回:0
func (this *RedisOperator) SDel(key string, members ...interface{}) (int64, error) {
	return this.clt.SRem(this.withDeadlineCtx(), key, members...).Result()
}

func (this *RedisOperator) SIsMember(key string, member interface{}) (bool, error) {
	return this.clt.SIsMember(this.withDeadlineCtx(), key, member).Result()
}

func (this *RedisOperator) SLen(key string) (int64, error) {
	return this.clt.SCard(this.withDeadlineCtx(), key).Result()
}

func (this *RedisOperator) GetSetMemberList(key string) ([]string, error) {
	return this.clt.SMembers(this.withDeadlineCtx(), key).Result()
}

// 移除并返回集合中的一个随机元素
func (this *RedisOperator) SPop(key string) (string, error) {
	clt := this.clt
	if clt == nil {
		return "", fmt.Errorf("尚未创建连接")
	}
	return clt.SPop(this.withDeadlineCtx(), key).Result()
}

// 成功：返回field的当前值
func (this *RedisOperator) HIncrby(key, field string, delta int64) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.HIncrBy(this.withDeadlineCtx(), key, field, delta).Result()
}

var movedRegexp = regexp.MustCompile(`MOVED (\d+) (\S+):(\d+)`)

func handleMovedError(err error) (string, string, error) {
	errstr := err.Error()
	if strings.HasPrefix(errstr, "MOVED") {
		match := movedRegexp.FindStringSubmatch(errstr)
		if len(match) == 4 {
			slot := match[1]
			host := match[2]
			port := match[3]
			newAddr := fmt.Sprintf("%s:%s", host, port)
			return slot, newAddr, nil
		}
	}
	return "", "", err
}

func (this *RedisOperator) HSet(key, field string, data string) (int64, error) {
	r, err := this.clt.HSet(this.withDeadlineCtx(), key, field, data).Result()
	return r, err
}

func (this *RedisOperator) HSetValues(key string, fieldvalues ...interface{}) (int64, error) {
	return this.clt.HSet(this.withDeadlineCtx(), key, fieldvalues...).Result()
}

func (this *RedisOperator) HSetUrlEncodeString(key string, s string) (int64, error) {
	vmap := gobase.NewStrMap()
	vmap.URLFormDecode(s)
	var fieldvalues []interface{}
	for k, v := range vmap {
		fieldvalues = append(fieldvalues, k, v)
	}
	return this.clt.HSet(this.withDeadlineCtx(), key, fieldvalues...).Result()
}

func (this *RedisOperator) SetNX(key string, val string, exp time.Duration) (bool, error) {
	return this.clt.SetNX(this.withDeadlineCtx(), key, val, exp).Result()
}

func (this *RedisOperator) Expire(key string, exp time.Duration) (bool, error) {
	return this.clt.Expire(this.withDeadlineCtx(), key, exp).Result()
}

func (this *RedisOperator) HDel(key, field string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.HDel(this.withDeadlineCtx(), key, field).Result()
}

func (this *RedisOperator) GetSetIfOrNilEvalSha() string {
	this.lk.RLock()
	defer this.lk.RUnlock()
	return this.setIfSha
}

func (this *RedisOperator) checkSetIf() (err error) {
	this.lk.RLock()
	ok := len(this.setIfSha) > 0
	this.lk.RUnlock()
	if !ok {
		this.lk.Lock()
		defer this.lk.Unlock()
		if len(this.setIfSha) > 0 {
			return nil
		}

		// v==false 表示值不存在
		scripttext := `
		local v = redis.call('GET', KEYS[1])
		if (v==false) or v == ARGV[1] or v == '' then 
		   redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[3])
           return 200;
		end
		return v`
		val, err := this.clt.ScriptLoad(this.withDeadlineCtx(), scripttext).Result()
		if err != nil {
			return err
		}
		this.setIfSha = val
	}
	return nil
}

func (this *RedisOperator) checkHSetIf() (err error) {
	this.lk.RLock()
	ok := len(this.setIfSha) > 0
	this.lk.RUnlock()
	if !ok {
		this.lk.Lock()
		defer this.lk.Unlock()
		if len(this.hsetIfSha) > 0 {
			return nil
		}

		// v==false 表示值不存在
		scripttext := `
		local v = redis.call('HGET', KEYS[1], ARGV[1])
		if (v==false) or v == ARGV[2] or v == '' then 
		   redis.call('HSET', KEYS[1], ARGV[3], ARGV[4])
           redis.call('EXPIRE',KEYS[1], ARGV[5])
           return 200;
		end
		return v`
		val, err := this.clt.ScriptLoad(this.withDeadlineCtx(), scripttext).Result()
		if err != nil {
			return err
		}
		this.hsetIfSha = val
	}
	return nil
}

// exp: -1是永久
func (this *RedisOperator) HSetIfOrNil(key string, field, ifval, field1, val1 string, exp time.Duration) (bool, error) {
	err := this.checkHSetIf()
	if err != nil {
		return false, err
	}
	cmd := this.clt.EvalSha(this.withDeadlineCtx(), this.hsetIfSha, []string{key}, field, ifval, field1, val1, exp.Seconds())
	val, err := cmd.Result()

	if val == nil || (err != nil && strings.Contains(err.Error(), "NOSCRIPT")) {
		this.lk.Lock()
		this.hsetIfSha = "" // 下次重新添加
		this.lk.Unlock()
		return false, err
	}
	if v, ok := val.(int64); ok {
		return v == 200, nil
	}
	if v, ok := val.(string); ok {
		return v == "", nil
	}
	return false, nil
}

func (this *RedisOperator) SetIfOrNil(key string, ifval, newval string, exp time.Duration) (bool, error) {
	err := this.checkSetIf()
	if err != nil {
		return false, err
	}
	cmd := this.clt.EvalSha(this.withDeadlineCtx(), this.setIfSha, []string{key}, ifval, newval, exp.Seconds())
	val, err := cmd.Result()

	if val == nil || (err != nil && strings.Contains(err.Error(), "NOSCRIPT")) {
		this.lk.Lock()
		this.setIfSha = "" // 下次重新添加
		this.lk.Unlock()
		return false, err
	}
	if v, ok := val.(int64); ok {
		return v == 200, nil
	}
	if v, ok := val.(string); ok {
		return v == "", nil
	}
	return false, nil
}

// redis 服务端是单线程操作, 所以可以视为安全的操作
func (this *RedisOperator) HIncrbyIf(key string, field string, delta int64, max int64) (int, error) {
	clt := this.clt
	scriptSha := this.hincrbyIfSha
	if len(scriptSha) == 0 {
		if atomic.CompareAndSwapInt32(&this.busyflag, 0, 1) {
			defer func() {
				atomic.StoreInt32(&this.busyflag, 0)
			}()
			scripttext := `local num = tonumber(redis.call('HGET', KEYS[1], ARGV[1]))
if num then
   if num + tonumber(ARGV[2]) > tonumber(ARGV[3]) then
	   return 0
   end
end    
redis.call('hincrby', KEYS[1], ARGV[1], ARGV[2])
return num + tonumber(ARGV[2])`
			val, err := clt.ScriptLoad(this.withDeadlineCtx(), scripttext).Result()
			if err != nil {
				return 0, err
			}
			scriptSha = val
			this.hincrbyIfSha = scriptSha
		}
	} else {
		time.Sleep(time.Millisecond)
		scriptSha = this.hincrbyIfSha
	}
	if len(scriptSha) == 0 {
		return 0, fmt.Errorf("脚本尚未完成创建, 请稍后调用")
	}
	cmd := clt.EvalSha(this.withDeadlineCtx(), scriptSha, []string{key}, field, delta, max)
	val, err := cmd.Result()
	if val == nil || (err != nil && strings.Contains(err.Error(), "NOSCRIPT")) {
		this.hincrbyIfSha = "" // 脚本不存在了, 下次调用需要重新创建;
		return 0, err
	}
	if v, ok := val.(int); ok {
		return v, nil
	}

	if v, ok := val.(int64); ok {
		return int(v), nil
	}

	return 0, fmt.Errorf("未知的数据格式%v", val)

}

func (this *RedisOperator) LIsMember(key string, member interface{}) (bool, error) {
	exists := false

	length, err := this.clt.LLen(this.withDeadlineCtx(), key).Result()
	if err != nil {
		return exists, err
	}

	// 使用 LRANGE 命令获取列表中的所有元素
	elements, err := this.clt.LRange(this.withDeadlineCtx(), key, 0, length-1).Result()
	if err != nil {
		return exists, err
	}

	// 遍历列表中的所有元素，检查是否存在目标成员
	for _, e := range elements {
		if e == member {
			exists = true
			break
		}
	}
	return exists, nil
}

func (this *RedisOperator) LLen(key string) (int64, error) {
	rval, err := this.clt.LLen(this.withDeadlineCtx(), key).Result()
	if err != nil {
		if err == redis.Nil { // redis中没有值
			return rval, nil
		}
	}
	return rval, err
}
func (this *RedisOperator) LPush(key string, data string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.LPush(this.withDeadlineCtx(), key, data).Result()
}

func (this *RedisOperator) RPush(key string, data string) (int64, error) {
	clt := this.clt
	if clt == nil {
		return 0, fmt.Errorf("尚未创建连接")
	}
	return clt.RPush(this.withDeadlineCtx(), key, data).Result()
}

func (this *RedisOperator) LPop(key string) (string, error) {
	rval, err := this.clt.LPop(context.Background(), key).Result()
	if err != nil {
		if err == redis.Nil { // redis中没有值
			return rval, nil
		}
	}
	return rval, err

}

func (this *RedisOperator) RPop(key string) (string, error) {
	rval, err := this.clt.RPop(context.Background(), key).Result()
	if err != nil {
		if err == redis.Nil { // redis中没有值
			return rval, nil
		}
	}
	return rval, err
}

func (this *RedisOperator) LRem(key string, value string) (int64, error) {
	return this.clt.LRem(context.Background(), key, 0, value).Result()

}
